﻿using System;
using System.Collections.Generic;
using System.Linq;
using CI.Interfaces;
using CI.Organism;

namespace CI.Mesopotamia
{
    /// <summary>
    /// abstract fitness function class that implments some of the base methods
    /// </summary>
    public abstract class FitnessFunction : IFitnessFunction
    {

        protected readonly SimEnv SimEnv = SimEnv.Instance;

        /// <summary>
        /// Return a list with the fittest one first then to the last
        /// Will only show organisms born, ignores seeds etc
        /// </summary>
        public virtual List<IOrganism> Fittest(IOrganismCollection organisms)
        {
            #region Argument Validators
            if (organisms == null)
                throw new ArgumentException("organisms");
            #endregion

            // sorted list results
            var sortedOrganisms = new SortedList<long, IOrganism>();

            // evaluate each of the organims and copy them into the sorted list
            // note organims only returns alive and dead organisms
            foreach (var organism in organisms)
            {
                EvaluateAndSortOrganism(sortedOrganisms, organism);
            }

            // now add the inactive ones
            foreach (var organism in organisms.Inactive)
            {
                EvaluateAndSortOrganism(sortedOrganisms, organism);
            }

            // return the list in decsending order
            return new List<IOrganism>(sortedOrganisms.Values.Reverse());
        }

        private void EvaluateAndSortOrganism(SortedList<long, IOrganism> sortedOrganisms, IOrganism organism)
        {
            //  make sure their isnt duplicate hash codes, else log them
            if (!sortedOrganisms.ContainsKey(organism.GetHashCode()))
            {
                // calc fitness
                var fitness = EvaluateFitnessAndUpdateInternalFitnessValue(organism);
                // if its positively fit give it a value
                if (fitness > 0)
                    fitness = fitness * 100000000;
                // add it to the list
                sortedOrganisms.Add(fitness + organism.GetHashCode(), organism);
            }
            else
            {
                SimEnv.TraceLogger.LogDebug(string.Format("Duplicate hashCode exists in Fittest organisms list for  {0}:{1} and {2}:{3} ", organism.Guid, EvaluateFitnessAndUpdateInternalFitnessValue(organism), sortedOrganisms[organism.GetHashCode()].Guid, EvaluateFitnessAndUpdateInternalFitnessValue(sortedOrganisms[organism.GetHashCode()])));
            }
        }


        /// <summary>
        /// Evaluate the orgainsms and update in... this could be collison detection/area covered/objects placed
        /// to be called every cycle or 
        /// </summary>
        /// <param name="organisms"></param>
        public abstract void UpdateOrganisms(IOrganismCollection organisms);

        /// <summary>
        /// Evaluate the organisms fitness
        /// If the organism has no neural network it will score long.MinValue;
        /// If the organism has no children it will score 0;
        /// this will also updated the organisms internal fitness value and it returns the organisms fitness value
        /// </summary>
        /// <param name="organism"></param>
        /// <returns>updated organisms fitness</returns>
        public virtual long EvaluateFitnessAndUpdateInternalFitnessValue(IOrganism organism)
        {

            #region Argument Validators
            if (organism == null)
                throw new ArgumentException("organism");
            #endregion

            // reset fitness value
            organism.FitnessValue = 0;

            // if no brain return nothing
            if (organism.NeuralSystem == null)
            {
                return organism.FitnessValue;
            }

            // check for any children
            var parentFound = IfParentAddFitness(organism);

            // if they are not parents then they get zero
            if (!parentFound)
            {
                organism.FitnessValue = 0;
                return organism.FitnessValue;
            }
            
            // sub their starting energy and add new energy
            organism.FitnessValue += organism.Energy - organism.InitEnergy;

            return organism.FitnessValue;
        }

        protected static bool IfParentAddFitness(IOrganism organism)
        {
            var isParent = false;

            if (organism.Organisms != null)
            {
                // check each of the organism
                // create a new list as organism.Organisms is a locking list
                var checkOrganisms = new List<IOrganism>(organism.Organisms);
                checkOrganisms.AddRange(organism.Organisms.Inactive);

                foreach (var checkOrganism in checkOrganisms)
                {
                    // check its parents to match against the one we have
                    foreach (var organismParent in checkOrganism.ParentOrganisms)
                    {
                        // if it is the parent add its Init energy for that org
                        if (organismParent.Guid == organism.Guid)
                        {
                            isParent = true;
                            organism.FitnessValue += checkOrganism.InitEnergy;
                        }
                    }
                }
            }
            return isParent;
        }
    }
}
